﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using E4D.AppFabric.Entities;
using Microsoft.ApplicationServer.Caching.AdminApi;
using Microsoft.ApplicationServer.Caching;
using System.Management.Automation;
using E4D.AppFabric.Entities.Cache_Host;
using E4D.AppFabric.Entities.Engine;


namespace MDCAdminTool
{
    public class CacheHost : PowerShellBase<CacheCluster , MdcCache>
    {
        #region Fields
        private string              _ServiceName;
        private Collection<string>  _Regions;
        private Collection<MdcCache> _CacheNames;
        private bool                _IsOpen;
        private bool                _IsLeadHost;
        private CacheCluster        _Cluster;
        private int                 _CachePort;
        private int                 _ClusterPort;
        private int                 _ArbitratorPort;
        private int                 _ReplicationPort;
        private long                _HighWM;
        private long                _LowhWM;
        private long                _Size;
        private HostCacheStats      _HostCacheStats;
        #endregion

        #region Properties
        public Collection<string> Regions
        {
            get { return _Regions; }
            set { _Regions = value; OnPropertyChanged( "Regions" ); }
        }
        public Collection<MdcCache> CacheNames
        {
            get { return _CacheNames; }
            set { _CacheNames = value; OnPropertyChanged( "CacheNames" ); }
        }
        public bool IsOpen
        {
            get { return _IsOpen; }
            set { _IsOpen = value; OnPropertyChanged( "IsOpen" ); }
        }
        public CacheCluster Cluster
        {
            get { return _Cluster; }
            set { _Cluster = value; OnPropertyChanged( "Cluster" ); }
        }
        public int CachePort
        {
            get { return _CachePort; }
            set { _CachePort = value; OnPropertyChanged( "CachePort" ); }
        }
        public int ClusterPort
        {
            get { return _ClusterPort; }
            set { _ClusterPort = value; OnPropertyChanged( "ClusterPort" ); }
        }
        public int ArbitratorPort
        {
            get { return _ArbitratorPort; }
            set { _ArbitratorPort = value; OnPropertyChanged( "ArbitratorPort" ); }
        }
        public int ReplicationPort
        {
            get { return _ReplicationPort; }
            set { _ReplicationPort = value; OnPropertyChanged( "ReplicationPort" ); }
        }
        public long HighWm
        {
            get { return _HighWM; }
            set { _HighWM = value; OnPropertyChanged( "HighWm" ); }
        }
        public long LowhWm
        {
            get { return _LowhWM; }
            set { _LowhWM = value; OnPropertyChanged( "LowhWm" ); }
        }
        public long Size
        {
            get { return _Size; }
            set { _Size = value; OnPropertyChanged( "Size" ); }
        }
        public string ServiceName
        {
            get { return _ServiceName; }
            set { _ServiceName = value; OnPropertyChanged( "ServiceName" ); }
        }
        public bool IsLeadHost
        {
            get { return _IsLeadHost; }
            set { _IsLeadHost = value; OnPropertyChanged( "IsLeadHost" ); }
        }
        public HostCacheStats HostCacheStats
        {
            get { return _HostCacheStats; }
            set { _HostCacheStats = value; OnPropertyChanged( "HostCacheStats" ); }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Cache Host Wrapper.
        /// </summary>
        /// <param name="hostName">The name of the cache host.</param>
        /// <param name="cachePort">The cache port number of the cache host.</param>
        public CacheHost( string hostName , int cachePort , string serviceName, ServiceStatus status )
        {
            Type        = CacheType.CacheHost;
            Name        = hostName;
            CachePort   = cachePort;
            ServiceName = serviceName;
            Status = status;
            Params      = new Dictionary<string , object>
                        {
                            {"HostName"  , Name},
                            {"CachePort" , CachePort}
                        };
        }
        public CacheHost( HostInfo hostInfo , CacheCluster cluster )
            : this( hostInfo.HostName , hostInfo.PortNo , hostInfo.ServiceName, hostInfo.Status )
        {
            Cluster = cluster;
            Output  = Cluster.Output;

            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                GetCacheNames(session);
                LoadConfig(session); //TODO:
            }
        }
        #endregion

        #region PowerShell Commands
        public HostInfo GetCacheHostInfo(ISession session)
        {
            var hostInfo = Invoke(session, "Get-CacheHost" , Params ).First();

            if ( hostInfo == null  ) return null;

            return hostInfo.BaseObject as HostInfo;
        }

        /// <summary>
        /// Returns the configuration information about the specified cache host.
        /// </summary>
        /// <returns>Returns the configuration information about the specified cache host.</returns>
        public override void LoadConfig()
        {
            try
            {
                using (var session = SessionFactory.getLocalSession())
                {
                    Cluster.UseCacheCluster(session);
                    LoadConfig(session);
                }

                //UpdateStatus();
            }
            catch ( Exception ex )
            {
                AddResultToLog( ex.Message );
            }
        }

        /// <summary>
        /// Returns the configuration information about the specified cache host.
        /// </summary>
        /// <returns>Returns the configuration information about the specified cache host.</returns>
        protected virtual void LoadConfig(ISession session)
        {
            try
            {
                Cluster.UseCacheCluster(session);
                var r = Invoke(session, "Get-CacheHostConfig", Params);

                if (r == null || r.Count == 0) return;

                var props = r[0].Properties;

                CachePort = (int)props["CachePort"].Value;
                ClusterPort = (int)props["ClusterPort"].Value;
                ArbitratorPort = (int)props["ArbitrationPort"].Value;
                ReplicationPort = (int)props["ReplicationPort"].Value;
                HighWm = (long)props["HighWatermark"].Value;
                Name = (string)props["HostName"].Value;
                IsLeadHost = (bool)props["IsLeadHost"].Value;
                LowhWm = (long)props["LowWatermark"].Value;
                ServiceName = (string)props["ServiceName"].Value;
                Size = (long)props["Size"].Value;

                //UpdateStatus();
            }
            catch (Exception ex)
            {
                AddResultToLog(ex.Message);
            }
        }

        /// <summary>
        /// Updates the cache host configuration to the values specified in the command parameters.
        /// Example: Set-CacheConfig -HostName CacheServer1 -CachePort 22233 -LowWM 80 
        /// </summary>
        /// <param name="clusterPort">The cluster port number that is used by the cache host.</param>
        /// <param name="newClusterPort">The new cache port number.</param>
        /// <param name="cacheSize">The total amount of memory allocated for storing data on the cache host, in units of megabytes (MB).</param>
        /// <param name="highWM">The high watermark percentage of memory, when eviction should begin evicting objects that have not yet expired.</param>
        /// <param name="lowWM">The low watermark percentage of memory, when eviction should begin evicting expired objects. After reaching HWM, when eviction should stop evicting objects that have not yet expired.</param>
        public override void SaveConfig()
        {
            var p = new Dictionary<string , object>
                        {
                            {"HostName" ,  Name},
                            {"CachePort", CachePort},
                            {"ClusterPortNumber", ClusterPort},
                            {"APort", ArbitratorPort},
                            {"ReplicationPortNumber", ReplicationPort},
                            {"CacheSize", Size},
                            {"HWM"      , HighWm},
                            {"LWM"      , LowhWm }
                        };

            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                Invoke(session, "Set-CacheHostConfig", p);
            }
            
            //LoadConfig();
            //UpdateStatus();
        }

        /// <summary>
        /// Starts the specified cache host service. After a cache server reboot, this command is required 
        /// to allow the cache host service to re-join a running cache cluster.
        /// </summary>
        public override void Start()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                Invoke(session, "Start-CacheHost" , Params );

                Status = GetCacheHostInfo(session).Status;
            
                this.Cluster.UpdateHostStatus(this);
            }

            //UpdateStatus();
        }

        /// <summary>
        /// Stops the specified cache host service.
        /// Note: The Stop-CacheHost command will not stop a cache host Windows service if it is 
        /// performing the cluster management role and stopping the cache host will cause the entire 
        /// cluster to shut down.
        /// </summary>
        public override void Stop()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                Invoke(session, "Stop-CacheHost", Params);

                Status = GetCacheHostInfo(session).Status;

                this.Cluster.UpdateHostStatus(this);
            }

            //UpdateStatus();

            //GetCacheNames();
        }

        /// <summary>
        /// Shows statistics for the specified cache host including: size (in bytes), number of caches, regions, cached objects, and cache misses.
        /// Note: See the next table for information about how to use this command with the CacheName parameter.
        /// </summary>
        public override CacheStats LoadStatistics()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                var r = Invoke(session, "Get-CacheStatistics", Params);
                if (r != null)
                {
                    var result = r.BuildHostCacheStats();

                    if (result != null && result.Count > 0)
                        HostCacheStats = result[0];
                }
                else
                    HostCacheStats = new HostCacheStats();

                return HostCacheStats;

            }
        }

        public void Add( string name )
        {
            Add( new MdcCache( this , name ) );
        }
        public override void Add( MdcCache cache )
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);
                var parameters = cache.InitNewCacheParam();
                if (cache.IsHighAvailability)
                    parameters.Add("Force", null);

                var result = Invoke(session, "New-Cache", parameters);
                Cluster.LoadChildren(session);
            }
        }
        public override void Add( CacheEntityBase item )
        {
            if ( item is MdcCache ) Add( item as MdcCache );
        }

        /// <summary>
        /// Creates a new named cache when the cluster is running. 
        /// All values except for CacheName are optional.
        /// </summary>
        /// <param name="name">The name of the cache</param>
        /// <param name="isHighAvailability">A value of true enables the high availability feature. A value of 0 disables the high availability feature. The default value is false.</param>
        /// <param name="eviction"> value of LRU enables Least Recently Used eviction. A value of none disables eviction. The default value is LRU. Warning: a value of none may cause a cache host to run out of memory.</param>
        /// <param name="TTL">The default time-out for objects stored in cache before they expire, in minutes. The default value is 10.</param>
        /// <param name="isExpirable">Use this parameter to disable expiration; otherwise expiration will be enabled at the default settings.</param>
        public void AddCacheName( string cacheName , bool isHighAvailability , bool notificationsEnabled , Eviction eviction , Int64 Ttl , bool isExpirable )
        {
            var newCache = 
                new MdcCache( cacheName )
               {
                   Parent               = this ,
                   NotificationsEnabled = notificationsEnabled ,
                   IsHighAvailability   = isHighAvailability ,
                   Eviction             = eviction ,
                   TTL                  = Ttl ,
                   IsExpirable          = isExpirable
               };

            Add( newCache );

        }

        /// <summary>
        /// Removes a named cache.
        /// </summary>
        public override void Remove( MdcCache cache )
        {
            Remove( cache as CacheEntityBase );
        }
        public override void Remove( CacheEntityBase item )
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Cluster.UseCacheCluster(session);

                Invoke(session, "Remove-Cache", new Dictionary<string, object> { { "CacheName", item.Name } });
                Cluster.LoadChildren(session);
            }
        }

        /// <summary>
        /// Lists all caches and regions that reside on the specified cache host.
        /// Note: See the next table for information about using this command with no parameters.
        /// </summary>
        protected void GetCacheNames(ISession session)
        {
			if(this.Cluster.Caches != null && Cluster.Caches.Count > 0)
			{
				if(CacheNames != null && CacheNames.Count > 0)
				{
					return;
				}

				Log("Trying to get from cluster");
				CacheNames = this.Cluster.Caches;
				return;
				
			}
			
            var r = Invoke(session, "Get-Cache" );
            var result = new Collection<MdcCache>();

            if ( r == null ) return;

            foreach ( var host in r )
            {
                if ( host.BaseObject is CacheInfo )
                {
                    result.Add( new MdcCache( this , host.BaseObject as CacheInfo ) );
                }
            }
            
			CacheNames = result;

        	Cluster.Caches = result;
        }
        public override void LoadChildren()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                GetCacheNames(session);
            }
        }

        protected virtual void LoadChildren(ISession session)
        {
            GetCacheNames(session);
        }


        #endregion

        //void UpdateStatus()
        //{
        //    Cluster.LoadCacheHosts();
        //    Status = Cluster.Hosts.Where( x => x.Name == Name ).First().Status;
        //}
    }
}
